
/*
 * Texture feedback demo
 * Simon Green 6/97
 * si@sgi.com
 *
 * Compile:
 * cc -o feedback feedback.c -lglut -lGLU -lGL -lXmu -lXext -lX11 -lm
 *
 * Description:
 * This is an old effect - it's kind of like pointing a video camera at a TV
 * displaying the signal from itself.
 *
 * It also demonstrates the OpenGL 1.1 glCopyTexImage2D function to copy
 * texture data direct from the framebuffer. You'll need a machine with
 * reasonably fast texture mapping for it to be fun.
 *
 * Usage:
 * Start it up, hold down the left mouse button and move the mouse up and down
 * and left and right slowly. Play with the menus. Enjoy!
 *
 * Left mouse button - zoom / rotate
 * Right mouse button - translate (advanced users only)
 *
 * Bugs:
 * Don't try resizing the window. Don't stare at it for too long.
 */

#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <GL/glut.h>

#ifdef GL_VERSION_1_1

/* Some <math.h> files do not define M_PI... */
#ifndef M_PI
#define M_PI 3.14159265
#endif

#define MAXSIZE 512             /* Set this to your maximum texture size (square)
*/
#define TEXT_SEED    "OpenGL"

float ang = 2.0;
float scale = 1.05;
float tx = 0.0, ty = 0.0;

int oldx, oldy;
int lmb = 0;
int mmb = 0;
int autospin = 0;
float atime = 0.0;

int smooth = 1;
int seedmode = 0;
float seedsize = 0.1;

int primtype = GL_LINES;
float primsize = 1.0;
int nprims = 10;

float r, g, b;
float dr, dg, db;
int randomcolours = 0;


/* returns a random floating point number between 0.0 and 1.0 */
float frand(void)
{
	return (float) (rand() / 32767.0);
}

void init_colours(float speed)
{
	r = frand();
	g = frand();
	b = frand();
	dr = frand() / speed;
	dg = frand() / speed;
	db = frand() / speed;
}

void bounce(float *n, float *dn)
{
	*n += *dn;
	if (*n > 1.0) {
		*n = 1.0;
		*dn = -*dn;
	}
	if (*n < 0.0) {
		*n = 0.0;
		*dn = -*dn;
	}
}

/* generate pretty colours by bouncing rgb values up and down */
void set_colour(void)
{
	if (randomcolours) {
		glColor3f(frand(), frand(), frand());
	} else {
		bounce(&r, &dr);
		bounce(&g, &dg);
		bounce(&b, &db);
		glColor3f(r, g, b);
	}
}


/* seed pattern with some random primitives in centre of screen */
void seed(void)
{
	int i;

	glBegin(primtype);
	for(i=0; i<nprims; i++) {
		set_colour();
		glVertex2f((frand() - 0.5) * seedsize, (frand() - 0.5) * seedsize);
	}
	glEnd();
}


/* seed pattern with a circular pattern */
void seed_circle(void)
{
	int i;
	double a;

	glBegin(primtype);
	for(i=0; i<nprims; i++) {
		a = ((double) i * 2 * M_PI) / nprims;
		glColor4f(0.0, 0.0, 0.0, 1.0);
		glVertex2d(0.0, 0.0);
		set_colour();
		glVertex2d(sin(a) * (seedsize / 2.0), cos(a) * (seedsize / 2.0));
	}
	glEnd();
}

/* bit of a silly one, this */
void seed_teapot(void)
{
	glLoadIdentity();
	glTranslatef((frand() - 0.5) * seedsize, (frand() - 0.5) * seedsize, 0.0);
	glRotatef(frand() * 360.0, frand(), frand(), frand());

	set_colour();
	glutWireTeapot(seedsize);
}


/* seed with text string */
void seed_text(char *string)
{
	int i;
	int width = 0;

	for (i = 0; i < strlen(string); i++) {
		width += glutStrokeWidth(GLUT_STROKE_ROMAN, string[i]);
	}

	glLoadIdentity();
	glScalef(seedsize / 100.0, seedsize / 100.0, seedsize / 100.0);
	glTranslatef(-width / 2.0, -50.0, 0.0);

	for (i = 0; i < strlen(string); i++) {
		set_colour();
		glutStrokeCharacter(GLUT_STROKE_ROMAN, string[i]);
	}
}



/* copy screen image to texture memory */
void grab_screen(void)
{
	glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 0, 0, MAXSIZE, MAXSIZE,
	                 0);

	if (smooth) {
		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	} else {
		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	}
	glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE,  GL_DECAL);
	glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST);
}


void reset(void)
{
	ang = 0.0;
	scale = 1.0;
	tx = ty = 0.0;
	autospin = 0;

	glClear(GL_COLOR_BUFFER_BIT);
	grab_screen();
}


void redraw(void)
{
	glClear(GL_COLOR_BUFFER_BIT);

	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	glTranslatef(tx, ty, 0.0);
	glRotatef(ang, 0.0, 0.0, 1.0);
	glScalef(scale, scale, scale);

	if (autospin) {
		ang = 3.0 * cos(atime);
		scale = 1.0 + ( sin(atime / 4.0) * 0.1) ;
		atime += 0.01;
	}

	/* draw feedback square */
	glEnable(GL_TEXTURE_2D);
	glBegin(GL_QUADS);
	glTexCoord2f(0.0, 0.0);
	glVertex2f(-1.0, -1.0);
	glTexCoord2f(1.0, 0.0);
	glVertex2f(1.0, -1.0);
	glTexCoord2f(1.0, 1.0);
	glVertex2f(1.0, 1.0);
	glTexCoord2f(0.0, 1.0);
	glVertex2f(-1.0, 1.0);
	glEnd();
	glDisable(GL_TEXTURE_2D);

	/* draw square outline */
	glColor3f(1.0, 1.0, 1.0);
	glBegin(GL_LINE_LOOP);
	glVertex2f(-1.0, -1.0);
	glVertex2f(1.0, -1.0);
	glVertex2f(1.0, 1.0);
	glVertex2f(-1.0, 1.0);
	glEnd();

	/* seed pattern */
	glLoadIdentity();
	switch(seedmode) {
	case 0:
		seed();
		break;
	case 1:
		seed_circle();
		break;
	case 2:
		seed_teapot();
		break;
	case 3:
		seed_text(TEXT_SEED);
		break;
	}

	/* grab screen as texture */
	grab_screen();

	glutSwapBuffers();
}


void mouse(int button, int state, int x, int y)
{
	if (button == GLUT_LEFT_BUTTON && state == GLUT_DOWN) {
		lmb = 1;
		oldx = x;
		oldy = y;
	}
	if (button == GLUT_LEFT_BUTTON && state == GLUT_UP) {
		lmb = 0;
	}

	if (button == GLUT_MIDDLE_BUTTON && state == GLUT_DOWN) {
		mmb = 1;
		oldx = x;
		oldy = y;
	}
	if (button == GLUT_MIDDLE_BUTTON && state == GLUT_UP) {
		mmb = 0;
	}

}

void motion(int x, int y)
{
	if (lmb) {
		ang += ((oldx - x) / 4.0 );
		scale += ((oldy - y) / 400.0);

		oldx = x;
		oldy = y;
		glutPostRedisplay();
	}
	if (mmb) {
		tx += ((float) (x - oldx)) / 500.0;
		ty += ((float) (oldy - y)) / 500.0;

		oldx = x;
		oldy = y;
		glutPostRedisplay();
	}
}


void main_menu(int i)
{
	switch(i) {
	case 1:
		autospin = !autospin;
		atime = 0.0;
		break;
	case 2:
		reset();
		break;
	case 3:
		exit(0);
	}
}

void mode_menu(int i)
{
	smooth = i;
}

void seed_menu(int i)
{
	seedmode = i;
}

void prim_menu(int i)
{
	primtype = i;
}

void size_menu(int i)
{
	seedsize = 1.0 / i;
}

void psize_menu(int i)
{
	primsize = (float) i;

	glPointSize(primsize);
	glLineWidth(primsize);
}


void no_menu(int i)
{
	nprims = i;
}


void colour_menu(int i)
{
	switch(i) {
	case 0:
		init_colours(500.0);
		randomcolours = 0;
		break;
	case 1:
		init_colours(100.0);
		randomcolours = 0;
		break;
	case 2:
		init_colours(10.0);
		randomcolours = 0;
		break;
	case 3:
		randomcolours = 1;
		break;
	}
}

int mainmenu;
int modemenu, seedmenu, primmenu, sizemenu, psizemenu, nomenu,
    colourmenu;

void create_menus(void)
{
	modemenu = glutCreateMenu(mode_menu);
	glutAddMenuEntry("Chunky", 0);
	glutAddMenuEntry("Smooth", 1);

	seedmenu = glutCreateMenu(seed_menu);
	glutAddMenuEntry("Primitives", 0);
	glutAddMenuEntry("Circle", 1);
	glutAddMenuEntry("Teapot", 2);
	glutAddMenuEntry("Text", 3);

	colourmenu = glutCreateMenu(colour_menu);
	glutAddMenuEntry("Slow", 0);
	glutAddMenuEntry("Medium", 1);
	glutAddMenuEntry("Fast", 2);
	glutAddMenuEntry("Random", 3);

	primmenu = glutCreateMenu(prim_menu);
	glutAddMenuEntry("Dots", GL_POINTS);
	glutAddMenuEntry("Lines", GL_LINES);
	glutAddMenuEntry("Triangles", GL_TRIANGLES);

	sizemenu = glutCreateMenu(size_menu);
	glutAddMenuEntry("Tiny", 20);
	glutAddMenuEntry("Small", 10);
	glutAddMenuEntry("Medium", 5);
	glutAddMenuEntry("Large", 2);

	nomenu = glutCreateMenu(no_menu);
	glutAddMenuEntry("1", 1);
	glutAddMenuEntry("2", 2);
	glutAddMenuEntry("3", 3);
	glutAddMenuEntry("5", 5);
	glutAddMenuEntry("10", 10);
	glutAddMenuEntry("20", 20);
	glutAddMenuEntry("30", 30);
	glutAddMenuEntry("50", 50);

	psizemenu = glutCreateMenu(psize_menu);
	glutAddMenuEntry("1", 1);
	glutAddMenuEntry("3", 3);
	glutAddMenuEntry("5", 5);

	mainmenu = glutCreateMenu(main_menu);
	glutAddSubMenu("Texture mode", modemenu);
	glutAddSubMenu("Seed mode", seedmenu);
	glutAddSubMenu("Seed size", sizemenu);
	glutAddSubMenu("Colours", colourmenu);

	glutAddMenuEntry("-----------", -1);

	glutAddSubMenu("Primitive", primmenu);
	glutAddSubMenu("Number", nomenu);
	glutAddSubMenu("Size", psizemenu);

	glutAddMenuEntry("-----------", 0);
	glutAddMenuEntry("Autospin", 1);
	glutAddMenuEntry("Reset", 2);
	glutAddMenuEntry("Quit", 3);

	glutAttachMenu(GLUT_RIGHT_BUTTON);
}


int main(int argc, char **argv)
{
	glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE);
	glutCreateWindow("feedback");
	glutReshapeWindow(MAXSIZE, MAXSIZE);
	glutDisplayFunc(redraw);
	glutIdleFunc(redraw);
	glutMouseFunc(mouse);
	glutMotionFunc(motion);

	create_menus();
	init_colours(100.0);

	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	glOrtho(-1.0, 1.0, -1.0, 1.0, 0.0, 1.0);
	glViewport(0, 0, MAXSIZE, MAXSIZE);

	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();

	glutMainLoop();
	return 0;             /* ANSI C requires main to return int. */
}

#else

int
main(int argc, char** argv)
{
	fprintf (stderr, "This program demonstrates a feature which is not in OpenGL Version 1.0.\n");
	fprintf (stderr, "If your implementation of OpenGL Version 1.0 has the right extensions,\n");
	fprintf (stderr, "you may be able to modify this program to make it run.\n");
	return 0;
}

#endif
